package com.suse.service.impl;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.suse.common.config.redis.RedisClient;
import com.suse.common.util.core.LongUtil;
import com.suse.common.util.mq.MessageBroker;
import com.suse.entity.SeckillVoucher;
import com.suse.entity.VoucherOrder;
import com.suse.mapper.VoucherOrderMapper;
import com.suse.result.Result;
import com.suse.result.ResultEnum;
import com.suse.service.SeckillVoucherService;
import com.suse.service.VoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.suse.util.RedisUtil;
import com.suse.util.UserHolder;
import org.redisson.api.RedissonClient;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements VoucherOrderService {

    private static final DefaultRedisScript<Long> SECKILL_SCRIPT = new DefaultRedisScript<>();

    static {
        SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }

    private final MessageBroker<VoucherOrder> messageBroker = new MessageBroker<>((voucherOrder) -> {
        VoucherOrderService orderService = SpringUtil.getBean(VoucherOrderService.class);
        orderService.seckillVoucherInDB(voucherOrder);
    });

    @Resource
    private SeckillVoucherService seckillVoucherService;

    @Resource
    private RedisClient redisClient;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    // 自注入 解决自调用事务失效问题
    @Resource
    private VoucherOrderService voucherOrderService;

    @Resource
    private RedissonClient redissonClient;

    @Override
    public Result seckillVoucher(long voucherId) {
        // 使用LUA脚本实现秒杀资格的判断进行Redis秒杀（库存预减、一人一单）
        Long userId = UserHolder.getUser().getId();
        String voucherStockKey = RedisUtil.getVoucherStockKey(voucherId);
        String voucherSeckilledUsersKey = RedisUtil.getVoucherSeckilledUsersKey(voucherId);
        Long result = stringRedisTemplate.execute(SECKILL_SCRIPT, ListUtil.of(voucherStockKey,voucherSeckilledUsersKey), String.valueOf(userId));
        // 没有秒杀资格或者Redis秒杀失败
        if (LongUtil.longValue(result) == 0) {
            return Result.error(ResultEnum.SECKILL_FAIL);
        }
        if (LongUtil.longValue(result) == 2) {
            return Result.error(ResultEnum.STOCK_EMPTY);
        }
        if (LongUtil.longValue(result) == 3) {
            return Result.error(ResultEnum.SECKILLED);
        }
        // 有秒杀资格异步进行数据库秒杀
        String voucherOrderGUIDKey = RedisUtil.getVoucherOrderGUIDKey();
        long voucherOrderId = redisClient.GUID(voucherOrderGUIDKey);
        // TODO 优惠券订单保存阻塞队列 异步执行数据库秒杀
        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setVoucherId(voucherId);
        voucherOrder.setId(voucherOrderId);
        voucherOrder.setUserId(userId);
        messageBroker.put(voucherOrder);
        return Result.success(String.valueOf(voucherOrderId));
        // String类intern()方法
        // 1.会把当前对象拷贝得到一个新对象放到常量池中
        // 2.之后每次调用intern()方法都会直接从常量池中找，没找到就重复操作1
        // 锁细化
        // 使用Redisson分布式锁实现一人一单
        // RLock lock = redissonClient.getLock("hmdp:voucher:lock:" + userId);
        // if (!lock.tryLock()) {
        //     return Result.error(ResultEnum.SECKILLED);
        // }
        // // 非阻塞的锁try-finally代码块不能包括获取锁的代码
        // try {
        //     return voucherOrderService.seckillOneVoucherOnePerson(voucherId);
        // }  finally {
        //     lock.unlock();
        // }
    }

    public void seckillVoucherInDB(VoucherOrder voucherOrder) {
        Long voucherId = voucherOrder.getVoucherId();
        // 开始秒杀
        seckillVoucherService.lambdaUpdate()
                // 解决自增自减的并发问题
                .setSql("stock = stock - 1")
                .eq(SeckillVoucher::getVoucherId, voucherId)
                // 乐观锁解决超卖问题
                .gt(SeckillVoucher::getStock,0)
                .update();
        // 创建订单
        // voucherOrder.setCreateTime(LocalDateTime.now());
        save(voucherOrder);
    }

    @Transactional
    public Result seckillOneVoucherOnePerson(long voucherId) {
        // 1.判断是否已经秒杀过
        Long userId = UserHolder.getUser().getId();
        Integer count = lambdaQuery().eq(VoucherOrder::getUserId, userId)
                .count();
        if (count > 0) {
            return Result.error(ResultEnum.SECKILLED);
        }
        // 1.查询优惠券信息
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        LocalDateTime beginTime = voucher.getBeginTime();
        LocalDateTime endTime = voucher.getEndTime();
        LocalDateTime now = LocalDateTime.now();
        // 2.判断优惠券秒杀是否开始
        if (now.isBefore(beginTime)) {
            return Result.error(ResultEnum.SECKILL_NOT_START);
        }
        // 3.判断优惠券秒杀是否结束
        if (now.isAfter(endTime)) {
            return Result.error(ResultEnum.SECKILL_OVER);
        }
        Integer stock = voucher.getStock();
        // 4.判断优惠券是否还有库存
        if (stock < 0) {
            return Result.error(ResultEnum.STOCK_EMPTY);
        }
        // 5.开始秒杀
        boolean update = seckillVoucherService.lambdaUpdate()
                // 解决自增自减的并发问题
                .setSql("stock = stock - 1")
                .eq(SeckillVoucher::getVoucherId, voucherId)
                // 乐观锁解决超卖问题
                .gt(SeckillVoucher::getStock,0)
                .update();
        // 6.判断秒杀是否成功
        if (!update) {
            return Result.error(ResultEnum.SECKILL_FAIL);
        }
        // 7.创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setUserId(userId);
        // voucherOrder.setUserId(UserHolder.getUser().getId());
        voucherOrder.setVoucherId(voucherId);
        // 8.生成全局唯一ID
        voucherOrder.setId(redisClient.GUID(RedisUtil.getVoucherOrderGUIDKey()));
        // voucherOrder.setCreateTime(LocalDateTime.now());
        save(voucherOrder);
        return Result.success(voucherOrder.getId());
    }

}
